﻿using System;
using System.IO;
using UtilZ.Dotnet.AudioPlayer.Native;
using UtilZ.Dotnet.Ex.Audio;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.AudioPlayer.Core
{
    /// <summary>
    /// 音频数据辅助类
    /// </summary>
    public class AudioDataHelper
    {
        /// <summary>
        /// 获取音频文件信息
        /// </summary>
        /// <param name="filePath">音频文件路径</param>
        /// <returns></returns>
        public static WaveInfo ChannelGetInfo(string filePath)
        {
            if (string.IsNullOrWhiteSpace(filePath))
            {
                throw new ArgumentNullException(nameof(filePath));
            }

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("音频文件不存在", filePath);
            }

            int handle = BASSHelper.StreamCreateFile(filePath);
            BASS_CHANNELINFO_INTERNAL info = BASSHelper.ChannelGetInfo(handle);
            BASSHelper.StreamFree(handle);
            return new WaveInfo(WaveEncoding.Pcm, info.freq, (short)info.chans, (short)info.origres)
            {
                FilePath = filePath
            };
        }





        private const double _PRE = 0d;

        /// <summary>
        /// 将文件由压缩格式转换为无压缩的线性数据格式
        /// </summary>
        /// <param name="srcFilePath">要转换的源文件路径</param>
        /// <param name="dstFilePath">转换结果文件路径</param>
        public static void ConvertToPcm(string srcFilePath, string dstFilePath)
        {
            PrimitiveSub(srcFilePath, dstFilePath, double.NaN, double.NaN);
        }

        /// <summary>
        /// 从文件中指定位置起始截取指定时长的数据,并保存到新文件
        /// /// </summary>
        /// <param name="srcFilePath">要转换的源文件路径</param>
        /// <param name="dstFilePath">转换结果文件路径</param>
        /// <param name="beginTimeMilliseconds">截取起始时刻,单位/毫秒[小于等于0或NaN或NegativeInfinity表示从起始位置开始截取]</param>
        /// <param name="durationMilliseconds">截取时长,单位/毫秒[NaN或PositiveInfinity表示截取到结束]</param>
        public static void Sub(string srcFilePath, string dstFilePath, double beginTimeMilliseconds, double durationMilliseconds)
        {
            if (double.IsPositiveInfinity(beginTimeMilliseconds))
            {
                throw new ArgumentOutOfRangeException($"截取起始时刻值{beginTimeMilliseconds}无效");
            }

            if (!double.IsNaN(durationMilliseconds) &&
                !double.IsPositiveInfinity(durationMilliseconds) &&
                durationMilliseconds < _PRE || double.IsNegativeInfinity(durationMilliseconds))
            {
                throw new ArgumentOutOfRangeException($"截取时长值{beginTimeMilliseconds}无效");
            }

            PrimitiveSub(srcFilePath, dstFilePath, beginTimeMilliseconds, durationMilliseconds);
        }

        private static void PrimitiveSub(string srcFilePath, string dstFilePath, double beginTimeMilliseconds, double durationMilliseconds)
        {
            if (string.IsNullOrWhiteSpace(srcFilePath))
            {
                throw new ArgumentNullException(nameof(srcFilePath));
            }

            if (string.IsNullOrWhiteSpace(srcFilePath))
            {
                throw new ArgumentNullException(nameof(dstFilePath));
            }

            if (!File.Exists(srcFilePath))
            {
                throw new FileNotFoundException("源文件不存在", srcFilePath);
            }

            if (File.Exists(dstFilePath))
            {
                throw new FileNotFoundException("目标文件已存在", dstFilePath);
            }

            PcmDataInfo pcmDataInfo = GetPcmDataByte(srcFilePath);
            if (beginTimeMilliseconds - pcmDataInfo.DurationSeconds * 1000 > _PRE)
            {
                throw new ArgumentOutOfRangeException($"起始时刻{beginTimeMilliseconds}超出范围");
            }

            int sampleBitByteCount = pcmDataInfo.SampleBit / 8;//采样位数所占字节数
            double secondByteCount = pcmDataInfo.SampleRate * pcmDataInfo.ChanelCount * sampleBitByteCount;//1秒数据长度,字节数
            double msByteCount = secondByteCount / 1000;//1毫秒数据长度,字节数

            int beginIndex;
            uint dataLength;
            if (beginTimeMilliseconds < _PRE ||
                double.IsNaN(beginTimeMilliseconds) ||
                double.IsNegativeInfinity(beginTimeMilliseconds))
            {
                //小于等于0或NaN或NegativeInfinity表示从起始位置开始截取
                beginIndex = 0;
            }
            else
            {
                beginIndex = (int)(beginTimeMilliseconds * msByteCount);
            }

            if (double.IsNaN(durationMilliseconds) || double.IsPositiveInfinity(durationMilliseconds))
            {
                //NaN或PositiveInfinity表示截取到结束
                dataLength = (uint)(pcmDataInfo.Data.Length - beginIndex);
            }
            else
            {
                dataLength = (uint)(durationMilliseconds * msByteCount);
                if (beginIndex + dataLength > pcmDataInfo.Data.Length)
                {
                    //截取长度超出范围
                    throw new ArgumentOutOfRangeException($"截取长度{durationMilliseconds}超出范围");
                    //dataSize = pcmDataInfo.Data.Length - beginIndex;
                }
            }

            using (FileStream fs = File.OpenWrite(dstFilePath))
            {
                WaveHelper.FillWavHead(fs, WaveEncoding.Pcm, pcmDataInfo.SampleBit, pcmDataInfo.SampleRate, (short)pcmDataInfo.ChanelCount, dataLength);

                uint writeLen = 100000000;//每次写入最大100MB
                while (dataLength > 0)
                {
                    if (writeLen > dataLength)
                    {
                        writeLen = dataLength;
                    }
                    fs.Write(pcmDataInfo.Data, beginIndex, (int)writeLen);
                    dataLength -= writeLen;
                }
                fs.Flush();
            }
        }



        /// <summary>
        /// 获取解码后的线性byte数据
        /// </summary>
        /// <returns>解码后的线性byte数据</returns>
        public static PcmDataInfo GetPcmDataByte(string filePath)
        {
            if (string.IsNullOrWhiteSpace(filePath))
            {
                throw new ArgumentNullException(nameof(filePath));
            }

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("源文件不存在", filePath);
            }

            int handle = BASSHelper.StreamCreateFile(filePath, 0, 0, BASSFlag.BASS_UNICODE | BASSFlag.BASS_STREAM_DECODE);
            try
            {
                long byteLength = BASSHelper.ChannelGetLength(handle, BASSMode.BASS_POS_BYTE);
                double durationSeconds = BASSHelper.ChannelBytes2Seconds(handle, byteLength);

                byte[] pcmData = new byte[byteLength];
                int count = BASSHelper.ChannelGetData(handle, pcmData, (int)byteLength);
                if (count != byteLength)
                {
                    ZLoger.Trace($"获取到文件\"{filePath}\"长度:{byteLength},读取到数据长度:{count},二者不一致,根据实际读取进行拷贝");
                    byte[] pcmDataSrc = pcmData;
                    pcmData = new byte[count];
                    Array.Copy(pcmDataSrc, pcmData, pcmData.Length);
                }

                BASS_CHANNELINFO_INTERNAL wavInfo = BASSHelper.ChannelGetInfo(handle);
                return new PcmDataInfo(wavInfo, durationSeconds, pcmData);
            }
            finally
            {
                BASSHelper.StreamFree(handle);
            }
        }

        /// <summary>
        /// 获取解码后的线性short数据
        /// </summary>
        /// <returns>解码后的线性short数据</returns>
        public static PcmDataInfo GetPcmDataShort(string filePath)
        {
            if (string.IsNullOrWhiteSpace(filePath))
            {
                throw new ArgumentNullException(nameof(filePath));
            }

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException("源文件不存在", filePath);
            }

            int handle = BASSHelper.StreamCreateFile(filePath, 0, 0, BASSFlag.BASS_UNICODE | BASSFlag.BASS_STREAM_DECODE);
            try
            {
                long byteLength = BASSHelper.ChannelGetLength(handle, BASSMode.BASS_POS_BYTE);
                double durationSeconds = BASSHelper.ChannelBytes2Seconds(handle, byteLength);

                int shortCount = (int)(byteLength);
                short[] pcmData = new short[shortCount];
                int count = BASSHelper.ChannelGetData(handle, pcmData, shortCount);

                //注:结果数据长度之所以除以2,是因为short=2byte,bass获取数据时必须使用字节长度,否则数据会少一半;
                //但是使用字节长度获取数据后,后一半数据又全是0,需要截取掉

                count = count / 2;
                short[] pcmDataSrc = pcmData;
                pcmData = new short[count];
                Array.Copy(pcmDataSrc, pcmData, count);

                BASS_CHANNELINFO_INTERNAL wavInfo = BASSHelper.ChannelGetInfo(handle);
                return new PcmDataInfo(wavInfo, durationSeconds, pcmData);
            }
            finally
            {
                BASSHelper.StreamFree(handle);
            }
        }


    }

}
